/*
 * Copyright 2009-2010 the original author or authors.
 *
 * Licensed under the Apache license, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.internna.iwebjtracker.model;

import java.util.Set;
import java.util.Date;
import java.util.Locale;
import java.util.HashSet;
import java.util.Collections;
import javax.persistence.Table;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Temporal;
import javax.persistence.OneToMany;
import javax.persistence.ManyToOne;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.NamedQuery;
import javax.persistence.PrePersist;
import javax.persistence.CascadeType;
import javax.persistence.TemporalType;
import org.hibernate.validator.NotEmpty;
import org.directwebremoting.annotations.RemoteProperty;
import org.directwebremoting.annotations.DataTransferObject;
import org.internna.iwebmvc.utils.CollectionUtils;
import org.internna.iwebmvc.metadata.ui.GridColumn;
import org.internna.iwebmvc.model.security.UserImpl;
import org.internna.iwebmvc.metadata.ui.EntityFilter;
import org.internna.iwebjtracker.project.model.Version;
import org.internna.iwebjtracker.project.validation.Errors;
import org.internna.iwebmvc.model.AbstractOwnedDomainEntity;
import org.internna.iwebjtracker.activity.model.RecordedActivity.ActivityType;

import static org.internna.iwebmvc.utils.StringUtils.hasText;

/**
 * Version implementation suitable to be loaded with milestones and issues.
 *
 * @author Jose Noheda
 * @since 1.0
 */
@Entity
@Table(name = "VERSIONS")
@DataTransferObject(javascript = "DefaultVersion")
@NamedQuery(name = "DefaultVersion.findAll", query = "SELECT v FROM DefaultVersion v")
public class DefaultVersion extends AbstractOwnedDomainEntity implements Version<TrackerUser, DefaultProject, DefaultVersion> {

    private static final long serialVersionUID = -1874934328735911446L;

    @NotEmpty
    @EntityFilter
    @RemoteProperty
    @Column(name = "LABEL")
    @GridColumn(relativeWidth = 1.2)
    private String label;

    @RemoteProperty
    @GridColumn(order = 2)
    @Column(name = "CURRENT_VERSION")
    @EntityFilter(type = EntityFilter.WIDGET_TYPE.BOOLEAN, order = 4)
    private boolean currentVersion;

    @RemoteProperty
    @GridColumn(order = 3)
    @Column(name = "DUE_DATE")
    @Temporal(TemporalType.DATE)
    @EntityFilter(range = true, type = EntityFilter.WIDGET_TYPE.DATE, order = 3)
    private Date dueDate;

    @ManyToOne
    @RemoteProperty
    @GridColumn(order = 4)
    @JoinColumn(name = "PROJECT", updatable = false)
    @EntityFilter(order = 2, type = EntityFilter.WIDGET_TYPE.ENTITY)
    private DefaultProject parentProject;

    @ManyToOne
    @RemoteProperty
    @GridColumn(order = 5)
    @JoinColumn(name = "PARENT_VERSION", updatable = false)
    private DefaultVersion parentVersion;

    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "parentVersion")
    private Set<DefaultVersion> milestones;

    @RemoteProperty
    @GridColumn(order = 6)
    @Column(name = "ISOPEN")
    private boolean open;

    /**
     * Accessor method
     */
    @Override public String getLabel() {
        return label;
    }

    /**
     * Accessor method
     */
    @Override public void setLabel(String label) {
        this.label = label;
    }

    /**
     * Accessor method
     */
    @Override public boolean isCurrentVersion() {
        return currentVersion;
    }

    /**
     * Accessor method
     */
    public void setCurrentVersion(boolean currentVersion) {
        this.currentVersion = currentVersion;
    }

    /**
     * Accessor method.
     *
     * @return any
     */
    @Override public DefaultProject getParentProject() {
        return parentProject;
    }

    /**
     * Accessor method. Clears parent version in the process.
     */
    @Override public void setParentProject(DefaultProject parentProject) {
        this.parentProject = parentProject;
        if ((parentVersion != null) && (!this.parentVersion.getParentProject().equals(this.parentProject))) {
            setParentVersion(null);
        }
    }

    /**
     * Accessor method
     *
     * @return a non null unmodifiable collection
     */
    @Override public Set<DefaultVersion> getMilestones() {
        return CollectionUtils.isEmpty(milestones) ? Collections.EMPTY_SET : Collections.unmodifiableSet(milestones);
    }

    /**
     * Clears the current collection of milestones and attaches a new one. All the milestones
     * are updated to reflect this new hierarchy.
     */
    public void setMilestones(Set<DefaultVersion> milestones) {
        if (CollectionUtils.isNotEmpty(this.milestones)) {
            this.milestones.clear();
        }
        this.milestones = milestones;
        if (milestones != null) {
            for (DefaultVersion milestone : milestones) {
                milestone.setParentVersion(this);
            }
        }
    }

    /**
     * Accessor method
     */
    public DefaultVersion getParentVersion() {
        return parentVersion;
    }

    /**
     * Accessor method. Resets parent project in the process.
     */
    public void setParentVersion(DefaultVersion parentVersion) {
        this.parentVersion = parentVersion;
        if (parentVersion != null) {
            setParentProject(parentVersion.getParentProject());
        }
    }

    /**
     * Returns the version label.
     *
     * @param locale any
     * @return a not empty string
     */
    @Override public String getCaption(Locale locale) {
        return getLabel();
    }

    /**
     * Accessor method
     */
    @Override public Date getDueDate() {
        return dueDate == null ? null : new Date(dueDate.getTime());
    }

    /**
     * Accessor method
     */
    @Override public void setDueDate(Date dueDate) {
        if (dueDate == null) {
            this.dueDate = null;
        } else if (dueDate.after(new Date())) {
            this.dueDate = new Date(dueDate.getTime());
        }
    }

    /**
     * Adds a previously created version to the milestone list
     *
     * @param version any (if null no operation is performed)
     * @return
     */
    @Override public DefaultVersion addMilestone(DefaultVersion version) {
        if (version != null) {
            if (milestones == null) {
                milestones = new HashSet<DefaultVersion>();
            }
            milestones.add(version);
            version.setParentProject(null);
            version.setParentVersion(this);
        }
        return version;
    }

    /**
     * Creates a valid milestone type and fills with default values.
     *
     * @param name any (if empty no operation is performed)
     * @param dueDate any
     * @return any
     */
    @Override public DefaultVersion createMilestone(String name, Date dueDate) {
        DefaultVersion version = null;
        if (hasText(name)) {
            version = new DefaultVersion();
            version.setLabel(name);
            version.setDueDate(dueDate);
            addMilestone(version);
        }
        return version;
    }

    /**
     * Accessor method.
     */
    public boolean isOpen() {
        return open;
    }

    /**
     * Accessor method.
     *
     * @param open any
     */
    public void setOpen(boolean open) {
        this.open = open;
    }

    /**
     *
     * @return
     */
    @Override public Errors close(TrackerUser user) {
       if ((user != null) && (getParentProject() != null) && (getParentProject().isAdministrator(user))) {
            setOpen(false);
            getParentProject().createActivity(ActivityType.VERSION_CLOSE, user);
        }
        return Errors.PROJECT_NOT_AN_ADMINISTRATOR;
    }

    /**
     * A version is closed if it is not open.
     *
     * @return !isOpen()
     */
    @Override public boolean isClosed() {
        return !isOpen();
    }

    /**
     * Invoked by the persistence provider to add entity security automatically.
     * Avoid calling this method.
     */
    @PrePersist
    protected final void setSecurity() {
        setOpen(true);
        setPublicView(getParentProject().isPublicView());
        for (UserImpl user : getParentProject().getOwners()) {
            addOwner(user);
        }
        if (!isPublicView()) {
            for (UserImpl user : getParentProject().getViewers()) {
                addViewer(user);
            }
        }
    }

    /**
     * Returns the version label.
     *
     * @return a non empty string
     */
    @Override public final String toString() {
        return getLabel();
    }

}
